import sys
import re
import fileinput
from itertools     import chain, combinations
from collections import defaultdict
import time; 


def subsets(arr):
    """ Returns non empty subsets of arr"""
    return chain(*[combinations(arr, i + 1) for i, a in enumerate(arr)])


def returnItemsWithMinSupport(itemSet, transactionList, minSupport, freqSet):
    """calculates the support for items in the itemSet and returns a subset of the itemSet 
    each of whose elements satisfies the minimum support"""
    _itemSet = set()
    localSet = defaultdict(int)

    for item in itemSet:
        for transaction in transactionList:
            if item.issubset(transaction):
                freqSet[item] += 1
                localSet[item] += 1

    for item, count in localSet.items():
        support = float(count) / len(transactionList)

        if support >= minSupport:
            _itemSet.add(item)

    return _itemSet



def joinSet(itemSet, length):
    """Join a set with itself and returns the n-element itemsets"""
    return set([i.union(j) for i in itemSet for j in itemSet if len(i.union(j)) == length])


def getItemSetTransactionList(data_iterator):
    transactionList = list()
    itemSet = set()
    for record in data_iterator:
        transaction = frozenset(record)
        transactionList.append(transaction)
        for item in transaction:
            itemSet.add(frozenset([item]))        # Generate 1-itemSets
    return itemSet, transactionList

def runApriori(data_iter, minSupport, minConfidence):
    """
    run the apriori algorithm. data_iter is a record iterator
    Return both: 
     - items (tuple, support)
     - rules ((pretuple, posttuple), confidence)
    """
    print "Hello"
    itemSet, transactionList = getItemSetTransactionList(data_iter)
    
    freqSet = defaultdict(int)
    holder = dict()
    largeSet = dict()                # Global dictionary which stores (key=n-itemSets,value=support) which satisfy minSupport
    assocRules = dict()                # Dictionary which stores Association Rules
    
    oneCSet = returnItemsWithMinSupport(itemSet, transactionList, minSupport, freqSet)
    
    currentLSet = oneCSet
    k = 2
    while(currentLSet != set([])):
        largeSet[k - 1] = currentLSet
        currentLSet = joinSet(currentLSet, k)
        currentCSet = returnItemsWithMinSupport(currentLSet, transactionList, minSupport, freqSet)
        currentLSet = currentCSet
        k = k + 1

    def getSupport(item):
            """local function which Returns the support of an item"""
            return float(freqSet[item]) / len(transactionList)

    toRetItems = []
    for key, value in largeSet.items():
        toRetItems.extend([(tuple(item), getSupport(item)) 
                           for item in value])

    toRetRules = []
    counter = 0
    for key, value in largeSet.items()[1:]:
        ruleSet = dict()
        counter += 1
        holder[counter]=ruleSet
        for item in value:
            _subsets = map(frozenset, [x for x in subsets(item)])
            for element in _subsets:
                remain = item.difference(element)
                if len(remain) > 0:
                    confidence = getSupport(item) / getSupport(element)
                    if confidence >= minConfidence:
                        toRetRules.append(((tuple(element), tuple(remain)),
                                           confidence))
                        length = len(element)
                        rules = holder[length]
                        if rules.has_key(element):
                            options = rules.get(element)
                            print "Old option"
                            print "Old option element ",element
                            print "Old option remain ",remain
                            options[remain]=confidence
                            
                            print "Old option option ", options.items()       
                        else:
                            print "New option"
                            options=dict()
                            options[remain]=confidence
                            rules[element]=options 
                            print "New option element ",element
                            print "New option remain ",remain
                            print "New option option ", options.items()       
    print "--------------------"
    print holder 
    return toRetItems, toRetRules, holder

def runApriorii(data_iter, minSupport, minConfidence):
    """
    run the apriori algorithm. data_iter is a record iterator
    Return both: 
     - items (tuple, support)
     - rules ((pretuple, posttuple), confidence)
    """
    print "Hello"
    
    itemSet, transactionList = getItemSetTransactionList(data_iter)
    
    freqSet = defaultdict(int)
    largeSet = dict()                # Global dictionary which stores (key=n-itemSets,value=support) which satisfy minSupport
    assocRules = dict()                # Dictionary which stores Association Rules
    
    oneCSet = returnItemsWithMinSupport(itemSet, transactionList, minSupport, freqSet)
    
    currentLSet = oneCSet
    k = 2
    while(currentLSet != set([])):
        largeSet[k - 1] = currentLSet
        currentLSet = joinSet(currentLSet, k)
        currentCSet = returnItemsWithMinSupport(currentLSet, transactionList, minSupport, freqSet)
        currentLSet = currentCSet
        k = k + 1

    def getSupport(item):
            """local function which Returns the support of an item"""
            return float(freqSet[item]) / len(transactionList)

    toRetItems = []
    for key, value in largeSet.items():
        toRetItems.extend([(tuple(item), getSupport(item)) 
                           for item in value])

    toRetRules = []
    for key, value in largeSet.items()[1:]:
        for item in value:
            _subsets = map(frozenset, [x for x in subsets(item)])
            for element in _subsets:
                remain = item.difference(element)
                if len(remain) > 0:
                    confidence = getSupport(item) / getSupport(element)
                    if confidence >= minConfidence:
                        toRetRules.append(((tuple(element), tuple(remain)),
                                           confidence))
    print "qqqq    ",toRetItems
    print "wwww    ",toRetRules
    return toRetItems, toRetRules


def printResults(items, rules):
    for item, support in items:
        print "item: %s , %.3f" % (str(item), support)
    print "\n------------------------ rules:"
    for rule, confidence in rules:
        pre, post = rule
        print "rule: %s ==> %s , %.3f" % (str(pre), str(post), confidence)

def test(items, rules, holder):
    for rule, confidence in rules:
#        print rule
        antecedent, consequent = rule
#        print str(antecedent)
#        print str(consequent)
        print "rule: %s ==> %s , %.3f" % (str(antecedent), str(consequent), confidence)
    print rules
    print "Recommendation  : "
#    print holder.items()
    print "if 48 comes ", recommend(['1','2'], holder)
 
def evaluate1(session,actual,holder,average,count):
    recommendationSet = recommend(session,holder)
    count+=1
    if actual in recommendationSet:
        average = calculateAccuracy(1,average,count)
        correct=1
        return (average,count,correct)
    else:
        average=calculateAccuracy(0,average,count)
        correct=0
        return (average,count,correct)
    
def evaluate(recommendationSet,actual,average,count,correct):
    count+=1
    if actual in recommendationSet:
        average = calculateAccuracy(1,average,count)
        correct+=1
        return (average,count,correct)
    else:
        average=calculateAccuracy(0,average,count)
        return (average,count,correct)
    
def calculateAccuracy(correct,average,count):
        return ((int(count)-1)*float(average)+int(correct))/int(count)

              
def recommend(session, holder):
    sessionSet = frozenset(session)
    length = len(sessionSet)
    if length > len(holder):
        return ""
    ruleSet = holder[length]
    if ruleSet.has_key(sessionSet):
        resultSet = ruleSet[sessionSet]
        recommendlist=frozenset()
        for key,item in resultSet.items():
            recommendlist|=key
        return recommendlist
    return ""
    
    
def dataFromFile(fname):
    """Function which reads from the file and yields a generator"""
    file_iter = open(fname, 'rU')
    for line in file_iter:
                line = line.strip().rstrip(',')                # Remove trailing comma
                record = frozenset(line.split(','))
                yield record

def dataloader(data):
    for element in data:
        record = frozenset(element)
        yield record

def loader(fname):
    file_iter = open(fname, 'rU')
    datalist = []
    for line in file_iter:
        line = line.strip().rstrip(',')
        templist = line.split(',')
        datalist.append(templist)
    return datalist

def aprioriRecommender(fname,holder):
    file_iter = open(fname, 'rU')
    count=0
    average=0
    total = 0
    correct =0
    print "Recommender : ", 1
    
    for line in file_iter:
        line = line.strip().rstrip(',')
        record = line.split(',')
        iterator =0
        sessionList =[]
        if len(record)>1:
            for item in record:
                iterator +=1
                print "Recommender Iterator : ", iterator
                if iterator==(len(record)-1):
                    break
                sessionList.append(item)
                print "Recommender session : ", sessionList
                recommendationList = recommend(sessionList, holder)
                total+=1
                print "Recommender recommendation : ", recommendationList
                if not (len(recommendationList)<1):
                    result= evaluate(recommendationList, record[iterator], average, count,correct)
                    average=result[0] 
                    count=result[1] 
                    correct=result[2]

    print "Average : ", average
    print "Total : ",total
    print "Count : ", count
    print "Correct : ", correct

if __name__ == "__main__":

    if len(sys.argv) < 5:
        print     """Insufficient Arguments\n
         Usage :\n
         \tpython apriori.py DATASET.csv minSupport minConfidence"""
        sys.exit('System will exit')
    
    minSupport = float(sys.argv[2])
    minConfidence = float(sys.argv[3])
    listdata = loader(sys.argv[1])
    print "1"
    localtime = time.asctime( time.localtime(time.time()) )
    print "Local current time :", localtime
    items, rules, holder = runApriori(dataloader(listdata), minSupport, minConfidence)
    localtime1 = time.asctime( time.localtime(time.time()) )
    print "Local current time :", localtime
    print "Local current time :", localtime1
    aprioriRecommender(sys.argv[4], holder)
    print "Local current time :", localtime
    print "Local current time :", localtime1
#    average,count,correct=evaluate(['1','2'], '5', holder, 0, 0)
#    print "Average : ", average
#    print "Count : ",count
#    print "Correct : ",correct
#    test(items, rules, holder)
#    items, rules    = runApriorii(dataFromFile(sys.argv[1]), minSupport, minConfidence)
#    printResults(items, rules)
